package com.hmsproject.db.mongodb;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

import org.bson.types.Binary;

import com.hmsproject.datastructures.HotelImage;
import com.hmsproject.datastructures.Hotel;
import com.hmsproject.datastructures.StarValue;
import com.hmsproject.datastructures.User;
import com.hmsproject.datastructures.deal.Deal;
import com.hmsproject.datastructures.enums.FacilitiesEnum;
import com.hmsproject.datastructures.enums.HotelSizeEnum;
import com.hmsproject.datastructures.enums.Hotels_DB_enum;
import com.hmsproject.datastructures.enums.User_DB_enum;
import com.hmsproject.db.HMSDBConnection;
import com.hmsproject.db.HMSDBConnectionFactory;
import com.hmsproject.db.HMSDBType;
import com.hmsproject.db.inmemorydb.DealMap;
import com.hmsproject.db.inmemorydb.HotelMap;
import com.hmsproject.db.mongodb.HMSMongoDBConstants;
import com.hmsproject.search.bean.Facilities;
import com.hmsproject.search.bean.FilterBean;
import com.hmsproject.search.bean.HotelInfo;
import com.hmsproject.search.bean.HotelSize;
import com.hmsproject.search.bean.HotelSorting;
import com.hmsproject.search.bean.PlaceBean;
import com.hmsproject.search.bean.PriceRange;
import com.hmsproject.search.bean.SearchBean;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mymobile.showcase.mobile.ForgotPasswordView;
import com.mymobile.showcase.mobile.SignInUserView;
import com.mymobile.showcase.mobile.SignUpUserView;


public class HMSMongoDBConnection extends HMSDBConnection{
	
	private static HMSMongoDBConnection hmsMongoDBConnection = new HMSMongoDBConnection();
	
	private DB db;
	
	private HMSMongoDBConnection()
	{
		try{
			@SuppressWarnings("deprecation")
			Mongo mongo = new Mongo( HMSMongoDBConstants.getInstance().getDbServerAddress() , HMSMongoDBConstants.getInstance().getDbServerPort() );
			System.out.println("DB ADDRESS:		" + HMSMongoDBConstants.getInstance().getDbServerAddress());
			db = mongo.getDB(HMSMongoDBConstants.getInstance().getDbName());
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public static HMSMongoDBConnection getInstance(){
		return hmsMongoDBConnection;
	}

	
	private DB getConnection(){
		return db;
	}
	
	public static void main(String args[]) {
		System.out.println(HMSMongoDBConnection.getInstance().testConnection());
	}
	
	
	
	@SuppressWarnings("deprecation")
	public boolean testConnection()  {
		
		boolean result = false;
		try{
			Mongo mongo = new Mongo( HMSMongoDBConstants.getInstance().getDbServerAddress() , HMSMongoDBConstants.getInstance().getDbServerPort() );
			DBObject ping = new BasicDBObject("ping", "1");
			mongo.getDB(HMSMongoDBConstants.getInstance().getDbName()).command(ping);
			result = true;
			
		}catch(Exception e){
			e.printStackTrace();
			
			result = false;
		}
		
		return result;

	}

	@Override
	public void updateUser(User user) {
		
		try{
			DBCollection userCollections = getConnection().getCollection(User_DB_enum.TABLE_NAME.getValue());
			
			BasicDBObject searchQuery = new BasicDBObject().append(User_DB_enum.EMAIL_ADDRESS.getValue(), user.getEmailAddress());
			
			BasicDBObject userDocument = new BasicDBObject()
			.append(User_DB_enum.FIRST_NAME.getValue(), user.getFirstName())
			.append(User_DB_enum.LAST_NAME.getValue(), user.getLastName())
			.append(User_DB_enum.MOBILEPHONE.getValue(), user.getMobilephone())
			.append(User_DB_enum.DOB.getValue(), user.getDob())
			.append(User_DB_enum.GENDER.getValue(), user.getGender())
			.append(User_DB_enum.COUNTRY.getValue(), user.getCountry())
			.append(User_DB_enum.ADDRESS.getValue(), user.getAddress())
		    .append(User_DB_enum.POSTCODE.getValue(), user.getPostcode())
		    .append(User_DB_enum.CITY.getValue(), user.getCity());
			
			BasicDBObject newDocument = new BasicDBObject();
			newDocument.append("$set", userDocument);
			
			userCollections.update(searchQuery,newDocument);
			
			System.out.println("USER GETS CREATED SUCCESSFULLY 	" + user.getEmailAddress());
		}catch(Exception e){
			e.printStackTrace();
		}
		// TODO Auto-generated method stub
		
	}
	
	@Override
	public User getUser(String emailAddress)
	{
		
		User user = new User();
		DBCollection userCollections = getConnection().getCollection(User_DB_enum.TABLE_NAME.getValue());
		
		BasicDBObject userQuery = new BasicDBObject().append(User_DB_enum.EMAIL_ADDRESS.getValue(), emailAddress);
		
		DBCursor dbcursor = userCollections.find(userQuery);
		
		if(dbcursor.hasNext()){
			DBObject dbObject = dbcursor.next();
			user.setFirstName((String)dbObject.get(User_DB_enum.FIRST_NAME.getValue()));
			user.setLastName((String)dbObject.get(User_DB_enum.LAST_NAME.getValue()));
			user.setEmailAddress((String)dbObject.get(User_DB_enum.EMAIL_ADDRESS.getValue()));
			user.setGender((String)dbObject.get(User_DB_enum.GENDER.getValue()));
			user.setAddress((String)dbObject.get(User_DB_enum.ADDRESS.getValue()));
			user.setCity((String)dbObject.get(User_DB_enum.CITY.getValue()));
			user.setCountry((String)dbObject.get(User_DB_enum.COUNTRY.getValue()));
			user.setDob((Date)dbObject.get(User_DB_enum.DOB.getValue()));
			user.setPostcode((String)dbObject.get(User_DB_enum.POSTCODE.getValue()));
			user.setMobilephone((String)dbObject.get(User_DB_enum.MOBILEPHONE.getValue()));
		}
		
		return user;
	}	

	@Override
	public boolean verifyUser(String verificationCode) throws Exception
	{
		boolean result = false;

		DBCollection userCollections = getConnection().getCollection(User_DB_enum.TABLE_NAME.getValue());
		
		BasicDBObject verificationCodeDocument = new BasicDBObject()
		.append(User_DB_enum.VERIFICATION_CODE.getValue(), Long.parseLong(verificationCode));
		
		DBCursor dbcursor = userCollections.find(verificationCodeDocument); 

		while(dbcursor.hasNext()){
			DBObject dbObject = dbcursor.next();
			BasicDBObject verifyUpdateDocument = new BasicDBObject();
			verifyUpdateDocument.append("$set", new BasicDBObject().append(User_DB_enum.VERIFIED.getValue(), "YES"));
			userCollections.update(verificationCodeDocument, verifyUpdateDocument);
			result = true;
		}
		
		return result;
	}	
	
	public boolean changePassword(String emailAddress,String newPassword)
	{
		boolean result = false;
		DBCollection userCollections = getConnection().getCollection(User_DB_enum.TABLE_NAME.getValue());
		
		BasicDBObject searchQuery = new BasicDBObject().append(User_DB_enum.EMAIL_ADDRESS.getValue(), emailAddress);
		
		BasicDBObject newPasswordDocument = new BasicDBObject()
		.append(User_DB_enum.PASSWORD.getValue(), newPassword);
		
		BasicDBObject newDocument = new BasicDBObject();
		newDocument.append("$set", newPasswordDocument);
		
		userCollections.update(searchQuery,newDocument);	
		
		result = true;
		
		return result;
	}		
	
	@Override
	public long signUpUser(SignUpUserView signUpUserView) throws Exception{
		
		long verificationCode = 0L; 

		DBCollection signUpUserCollections = getConnection().getCollection(User_DB_enum.TABLE_NAME.getValue());
		
		
		BasicDBObject checkIfUserAlreadyExistsDocument = new BasicDBObject();
		checkIfUserAlreadyExistsDocument.append(User_DB_enum.EMAIL_ADDRESS.getValue(), signUpUserView.getEmailAddress());
		
		DBCursor dbcursor = signUpUserCollections.find(checkIfUserAlreadyExistsDocument); 
		
		if(dbcursor.hasNext()){
			throw new Exception("User " + signUpUserView.getEmailAddress() + " already exists.");
		}
		
		verificationCode = new Random().nextLong();
		
		BasicDBObject signUpUserDocument = new BasicDBObject(User_DB_enum.FIRST_NAME.getValue(), signUpUserView.getFirstName())
		.append(User_DB_enum.LAST_NAME.getValue(), signUpUserView.getLastName())
		.append(User_DB_enum.PASSWORD.getValue(), signUpUserView.getPassword())
		.append(User_DB_enum.EMAIL_ADDRESS.getValue(), signUpUserView.getEmailAddress())
		.append(User_DB_enum.MOBILEPHONE.getValue(), null)
		.append(User_DB_enum.DOB.getValue(), null)
		.append(User_DB_enum.GENDER.getValue(), null)
		.append(User_DB_enum.COUNTRY.getValue(), null)
	    .append(User_DB_enum.POSTCODE.getValue(), null)
	    .append(User_DB_enum.CITY.getValue(), null)
	    .append(User_DB_enum.VERIFICATION_CODE.getValue(), verificationCode)
	    .append(User_DB_enum.VERIFIED.getValue(), "NO");
		
		signUpUserCollections.insert(signUpUserDocument);
		return verificationCode;
	}	
	
	@Override
	public boolean signInUser(SignInUserView signInUserView) {
		
		boolean loginSuccessful = false;
		DBCollection signInUserCollections = getConnection().getCollection(User_DB_enum.TABLE_NAME.getValue());
		BasicDBObject findUserQuery = new BasicDBObject();
		findUserQuery.append(User_DB_enum.EMAIL_ADDRESS.getValue(),signInUserView.getEmailAddress());
		findUserQuery.append(User_DB_enum.PASSWORD.getValue(),signInUserView.getPassword());
		findUserQuery.append(User_DB_enum.VERIFIED.getValue(),"YES");
		System.out.println("SIGN IN QUERY:   " + findUserQuery.toString());
		DBCursor dbcursor = signInUserCollections.find(findUserQuery); 
		
		if(dbcursor.hasNext()){
			loginSuccessful = true;
		}
		
		return loginSuccessful;

	}		
	
	@Override
	public String forgotPassword(ForgotPasswordView forgotPasswordView) throws Exception{
		
		String password = null;
		
		DBCollection forgotPasswordCollections = getConnection().getCollection(User_DB_enum.TABLE_NAME.getValue());
		BasicDBObject checkIfUserExistsQuery = new BasicDBObject();
		checkIfUserExistsQuery.append(User_DB_enum.EMAIL_ADDRESS.getValue(),forgotPasswordView.getEmailAddress());
		
		DBCursor dbcursor = forgotPasswordCollections.find(checkIfUserExistsQuery); 
		
		if(dbcursor.hasNext()){
			DBObject dbObject = dbcursor.next();
			password = (String)dbObject.get(User_DB_enum.PASSWORD.getValue());
		}else{
			throw new Exception("User " + forgotPasswordView.getEmailAddress() + " does not exist.");
		}
		
		return password;
	}
	
	
	@Override
	public void insertHotel(Hotel hotels){
		try{
			DBCollection hotelCollections = getConnection().getCollection(Hotels_DB_enum.TABLE_NAME.getValue());
			
			BasicDBObject hotelDocument = new BasicDBObject(Hotels_DB_enum.NAME.getValue(), hotels.getName())
			.append(Hotels_DB_enum.PLACE_NAME.getValue(), hotels.getPlaceName())
			.append(Hotels_DB_enum.PRICE.getValue(), hotels.getPrice())
			.append(Hotels_DB_enum.STAR_RATING.getValue(), hotels.getStarRating())
			.append(Hotels_DB_enum.HOTEL_SIZE.getValue(), hotels.getHotelSize().getValue())
			.append(Hotels_DB_enum.HOTEL_IMAGE_URL.getValue(), hotels.getHotelImage().getFilePath())
			.append(Hotels_DB_enum.HOTEL_IMAGE_OBJECT.getValue(), (Binary)hotels.getHotelImage().getImage())
		    .append(Hotels_DB_enum.POPULARITY.getValue(), hotels.getStarValue().getPopularity())
		    .append(Hotels_DB_enum.DISTANCE.getValue(), hotels.getStarValue().getDistance())
		    .append(Hotels_DB_enum.REVIEW.getValue(), hotels.getStarValue().getReview());
			
			List<FacilitiesEnum> faclitiesList = hotels.getFaclitiesList();
			
			if(faclitiesList.contains(FacilitiesEnum.SPA)){
				hotelDocument.append(Hotels_DB_enum.SPA.getValue(), "YES");
			}else
			{
				hotelDocument.append(Hotels_DB_enum.SPA.getValue(), "NO");
			}

			if(faclitiesList.contains(FacilitiesEnum.WIFI)){
				hotelDocument.append(Hotels_DB_enum.WIFI.getValue(), "YES");
			}else
			{
				hotelDocument.append(Hotels_DB_enum.WIFI.getValue(), "NO");
			}
			
			if(faclitiesList.contains(FacilitiesEnum.BREAKFAST)){
				hotelDocument.append(Hotels_DB_enum.BREAKFAST.getValue(), "YES");
			}else
			{
				hotelDocument.append(Hotels_DB_enum.BREAKFAST.getValue(), "NO");
			}
			
			if(faclitiesList.contains(FacilitiesEnum.POOL)){
				hotelDocument.append(Hotels_DB_enum.POOL.getValue(), "YES");
			}else
			{
				hotelDocument.append(Hotels_DB_enum.POOL.getValue(), "NO");
			}
			
			if(faclitiesList.contains(FacilitiesEnum.BEACH)){
				hotelDocument.append(Hotels_DB_enum.BEACH.getValue(), "YES");
			}else
			{
				hotelDocument.append(Hotels_DB_enum.BEACH.getValue(), "NO");
			}			
			
			hotelCollections.insert(hotelDocument);
			
			System.out.println("Hotel GETS CREATED SUCCESSFULLY 	" + hotels.getName());
		}catch(Exception e){
			e.printStackTrace();
		}
		
		
		
	}
	
	@Override
	public ArrayList<Hotel> searchHotels(SearchBean searchBean) {
		
		ArrayList<Hotel> hotelSearchList = new ArrayList<Hotel>();
		
		try{
			DBCollection hotelCollections = getConnection().getCollection(Hotels_DB_enum.TABLE_NAME.getValue());
			BasicDBObject filterquery = new BasicDBObject();
			BasicDBObject sortquery = new BasicDBObject();
			
			setSearchBeanProcessing(searchBean,filterquery,sortquery);

			DBCursor dbcursor = hotelCollections.find(filterquery); 
			dbcursor.sort(sortquery);
			
			while(dbcursor.hasNext()){
				DBObject dbObject = dbcursor.next();
				String name=  (String)dbObject.get(Hotels_DB_enum.NAME.getValue());
				System.out.println("HOTEL NAME " + name);
				
				String placeName = (String)dbObject.get(Hotels_DB_enum.PLACE_NAME.getValue());
				int price = ((Integer)dbObject.get(Hotels_DB_enum.PRICE.getValue())).intValue();
				
				int starRating = ((Integer)dbObject.get(Hotels_DB_enum.STAR_RATING.getValue())).intValue();
								
				HotelSizeEnum hotelSize =  null;
				String HotelSizeValue = (String)dbObject.get(Hotels_DB_enum.HOTEL_SIZE.getValue());
				if(HotelSizeValue.equals(HotelSizeEnum.FIFTY.getValue())){
					hotelSize  = HotelSizeEnum.FIFTY;
				}else
				if(HotelSizeValue.equals(HotelSizeEnum.FIFTY_ONE_FIFTY.getValue())){
					hotelSize  = HotelSizeEnum.FIFTY_ONE_FIFTY;
				}else{
					hotelSize  = HotelSizeEnum.MORE_THAN_ONE_FIFTY;
				}
				
				
				HotelImage hotelImage = new HotelImage((String)dbObject.get(Hotels_DB_enum.HOTEL_IMAGE_URL.getValue()), (Object)dbObject.get(Hotels_DB_enum.HOTEL_IMAGE_OBJECT.getValue()));
				StarValue starValue = new StarValue(((Integer)dbObject.get(Hotels_DB_enum.POPULARITY.getValue())).intValue(), ((Integer)dbObject.get(Hotels_DB_enum.DISTANCE.getValue())).intValue(),
						((Integer)dbObject.get(Hotels_DB_enum.REVIEW.getValue())).intValue());
				
				List<FacilitiesEnum> faclitiesList = new ArrayList<FacilitiesEnum>();
				String spa = (String)dbObject.get(Hotels_DB_enum.SPA.getValue());
				if(spa.equals("YES")){
					faclitiesList.add(FacilitiesEnum.SPA);
				}
				String wifi = (String)dbObject.get(Hotels_DB_enum.WIFI.getValue());
				if(wifi.equals("YES")){
					faclitiesList.add(FacilitiesEnum.WIFI);
				}
				String breakfast = (String)dbObject.get(Hotels_DB_enum.BREAKFAST.getValue());
				if(breakfast.equals("YES")){
					faclitiesList.add(FacilitiesEnum.BREAKFAST);
				}
				
				String pool = (String)dbObject.get(Hotels_DB_enum.POOL.getValue());
				if(pool.equals("YES")){
					faclitiesList.add(FacilitiesEnum.POOL);
				}
				
				String beach = (String)dbObject.get(Hotels_DB_enum.BEACH.getValue());
				if(beach.equals("YES")){
					faclitiesList.add(FacilitiesEnum.BEACH);
				}
				
				Hotel hotel = new Hotel("0",name,placeName,price,starRating,hotelSize,
						hotelImage,starValue,faclitiesList);
			
				hotelSearchList.add(hotel);
			}
		}catch(Exception e){
			e.printStackTrace();
		}
			
		

		return hotelSearchList;
	}	
	
	private void setSearchBeanProcessing(SearchBean searchBean,BasicDBObject filterquery,BasicDBObject sortquery){
		
		processPlaceBean(searchBean.getPlaceBean(), filterquery);
		processFilterBean(searchBean.getFilterBean(), filterquery);
		processHotelSorting(searchBean.getHotelSorting(), filterquery,sortquery);		
		
	}
	
	private void processHotelSorting(HotelSorting hotelSorting,BasicDBObject filterquery,BasicDBObject sortquery){
		
		if(hotelSorting == null){
			return;
		}
		
		if(hotelSorting == HotelSorting.NAME_A_TO_Z){
			sortquery.put(Hotels_DB_enum.NAME.getValue(), 1);
		}else
		if(hotelSorting == HotelSorting.POPULARITY){
			sortquery.put(Hotels_DB_enum.POPULARITY.getValue(), -1);
		}else
		if(hotelSorting == HotelSorting.DISTANCE){
				sortquery.put(Hotels_DB_enum.DISTANCE.getValue(), -1);
		}else
		if(hotelSorting == HotelSorting.REVIEW_SCORE){
				sortquery.put(Hotels_DB_enum.REVIEW.getValue(), -1);
		}else
		if(hotelSorting == HotelSorting.PRICE_LOW_TO_HIGH){
				sortquery.put(Hotels_DB_enum.PRICE.getValue(), 1);
		}else
		if(hotelSorting == HotelSorting.PRICE_HIGH_TO_LOW){
				sortquery.put(Hotels_DB_enum.PRICE.getValue(), -1);
		}else
		if(hotelSorting == HotelSorting.STAR_ZERO_TO_FIVE){
				sortquery.put(Hotels_DB_enum.STAR_RATING.getValue(), 1);
		}else
		if(hotelSorting == HotelSorting.STAR_FIVE_ZERO){
				sortquery.put(Hotels_DB_enum.STAR_RATING.getValue(), -1);
		}			
	}
	
	private void processFilterBean(FilterBean filterBean,BasicDBObject filterquery){
		
		if(filterBean == null)
		{
			return;
		}
		
		if(filterBean.getStarRating() != null)
		{
			filterquery.append(Hotels_DB_enum.STAR_RATING.getValue(),Integer.parseInt(filterBean.getStarRating()));
		}
		
		PriceRange priceRange = filterBean.getPriceRange();
		if(priceRange != null){
			filterquery.append(Hotels_DB_enum.PRICE.getValue(), new BasicDBObject("$gte", Integer.parseInt(priceRange.getLowerBound())).append("$lte", Integer.parseInt(priceRange.getUpperBound())));
		}
		
		HotelSizeEnum hotelSize = filterBean.getHotelSize();
		if(hotelSize != null){
			filterquery.append(Hotels_DB_enum.HOTEL_SIZE.getValue(),hotelSize.getValue());
		}
		
		List<String> facilities = filterBean.getFacilities();
		if(facilities != null && facilities.size() > 0){
			
				for (String element : facilities) {
					filterquery.append(element,"YES");
				}							
		}
	}
	
	@Override
	public List<HotelInfo> searchHotelsExpedia(SearchBean searchBean)
	{
		int[] dealMapperInex = {1,4,7,9};
		List<Hotel> hotelList = HMSDBConnectionFactory.getInstance().getDBConnection(HMSDBType.EXPEDIADB).searchHotels(searchBean);
		
		
		List<HotelInfo> hotelInfoList = new ArrayList<>();
		
		if(hotelList.isEmpty()){
			return hotelInfoList;
		}
		
		List<Deal> deals= new ArrayList<Deal>(DealMap.INSTANCE.getDealMapper().values());
		
		   for(Hotel hotel:hotelList){
			   hotelInfoList.add(new HotelInfo(hotel));
		   }
		
		   for (int i = 0; i < dealMapperInex.length; i++) {
			       Hotel hotel = hotelList.get(dealMapperInex[i]);
			       hotel.setHasDeal(true);
				   deals.get(i).setHotelId(hotel.getHotelId());
		   }		
		HotelMap.getInstance().setHotelMapper(hotelList);
		return hotelInfoList;
	}	
		
	
	private void processPlaceBean(PlaceBean placeBean,BasicDBObject filterquery){

		if(placeBean == null){
			return;
		}
		
		if(placeBean.getPlaceName() != null){
			filterquery.append(Hotels_DB_enum.PLACE_NAME.getValue(),placeBean.getPlaceName());
		}

	}
}